본문으로 건너뛰기

12장 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표

1. 웹사이트와 성능​

1. 지향해야 할 세 가지 수칙​

  • 사용자가 방문한 목적을 손쉽게 달성할 수 있어야 한다.

  • 방문 목적 달성에 걸리는 시간이 짧아야 한다.

  • 사용자의 정보가 누출되는 등의 사고 없이 보안이 철저해야 한다.

2, 개발자가 성능에 관심을 가져야 하는 이유​

  • 개발 도구는 사용자의 기기 보다 성능이 뛰어나기 때문에 성능 문제를 경험하기 어려워 문제를 파악하기 어렵다.

  • 성능 향상 작업은 러닝 커브가 높고 서비스를 개발하는 작업에 비해 눈에 띄는 성능 향상을 기대하기 어려워 많은 개발자가 기피하는 작업이다.

  • 하지만 웹 사이트의 성능은 조직이 이루고자 하는 목표와 직결되기 때문에 개발자는 웹 사이트의 성능에도 주의를 기울여 안정적인 사용자 경험을 위해 노력해야 한다.

2. 핵심 웹 지표(Core Web Vital)​

구글에서 만든 지표로 좋은 웹 사이트를 분류하는 기준으로 사용된다.

1.핵심 지표​

  • 최대 콘텐츠풀 페인트(LCP: Largest Contentful Paint)

  • 최초 입력 지연(FID: First Input Delay)

  • 누적 레이아웃 이동(CLS: Cumulative Layout Shift)

2. 특정 문제 진단 지표​

  • 최초 바이트까지의 시간(TTFB: Time To First Byte)

  • 최초 콘텐츠풀 시간(FCP: First Contentful Paint)

3. 최대 콘텐츠풀 페인트(LCP)​

1. 정의​

사용자 화면(뷰포트) 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는 데 얼마나 걸리는지를 측정하는 지표다.

2. 의미​

사용자에게 메인 콘텐츠의 정보를 화면에 전달하는 속도를 객관적으로 판단하기 위해 만들어진 지표다.

3. 예제​

LCP는 일반적으로 페이지에서 가장 크고 눈에 띄는 요소이다. 요소의 로딩 속도를 높이는 것으로 지표의 값이 달라진다.

function getLCPCandidate() {
let largestElement = null;
let maxArea = 0;

document.querySelectorAll("img, video, h1, h2, h3, p, div").forEach((el) => {
const rect = el.getBoundingClientRect();
const area = rect.width * rect.height;

if (area > maxArea) {
largestElement = el;
maxArea = area;
}
});

return largestElement;
}

// 페이지 로드 후 LCP 예상 요소 출력
window.addEventListener("load", () => {
const lcpElement = getLCPCandidate();
console.log("예상 LCP 요소:", lcpElement);
});
  • PerformanceObserver: API 를 활용해 실제 LCP를 감지
  • getBoundingClientRect(): 가장 큰 요소를 감지
<!-- 중요 이미지에 preload 적용 -->
<link rel="preload" as="image" href="large-image.jpg" />
<img src="large-image.jpg" loading="lazy" width="800" height="600" />
  • LCP 요소가 이미지인 경우 preload를 사용해 브라우저가 요소를 미리 로드하게 만들어 시간을 단축시킬 수 있다.

4. 기준 점수​

5. 개선 방안​

1. 텍스트는 언제나 옳다.​

텍스트는 어떤 요소보다 로딩이 빠르기 때문에 LCP 예상 영역에 문자열을 채우면 사용자에게 텍스트를 빠르게 노출하기 때문에 점수가 높아진다.

2. 이미지를 어떻게 불러올 것인가?​

1. 프리로드 스캐너​
  • HTML을 해석(파싱)하는 동안, 필요한 리소스를 미리 찾고 로드하는 브라우저 기능
  • <img> 태그 내부의 src 속성을 보면 브라우저가 즉시 다운로드 시작한다.
  • 따라서 LCP 요소가 <img>일 경우 빠르게 로딩되어 좋은 성능을 기대할 수 있음
<!-- 1) img -->
<img src="lcp.jpg" />
<!-- 2) svg -->
<svg xmlns="http://www.w3.org/1000/svg">
<image href="lcp.jpg" />
</svg>
<!-- 3) (비디오의 경우) vide.poster -->
<video poster="lcp.jpg"></video>
<!-- 4) background-image: url() -->
<div style="background-image: url(lcp.jpg)">...</div>
  • <img> : 프리로드 스캐너 덕분에 빠르게 로드된다.

  • <svg>: 내부에 <img>를 넣으면 프리로드 스캐너가 감지하지 못해 LCP에 불리하다.

  • <video>: 프리로드 스캐너 덕분에 빠르게 로드된다.

  • background-image: url() : css 리소스는 늦게 다운로드 되기 때문에 LCP에 불리하다.

3. 그 밖에 조심해야 할 사항​

  • loading=lazy는 로딩을 늦추는 옵션으로 LCP에 불리하기 때문에 중요한 곳에는 사용하지 않는 것이 좋다.

  • fadein과 같은 각종 애니메이션: fadeIn ease 10s과 같이 처리한다면 들어간 값 만큼 느려진다.

  • 외부 요청시 출처 수행을 하기 때문에 리소스는 가능하면 도메인에서 직접 호스팅 하는 것이 좋다.

  • 클라이언트에서 빌드하면 그만큼 시간이 지연되기 때문에 빌드 영역은 서버에서 하는 것이 좋다.

4. 최초 입력 지연(FID)​

1. 정의​

사용자가 페이지와 처음 상호 작용할 때 해당 상호 작용에 대한 응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 측정한다.

2. 의미​

화면이 그려지고 난 뒤 상호작용을 수행할 때 메인 스레드가 이벤트에 대한 반응을 할 수 있을 때까지 걸리는 시간이다.

3. 예제​

사용자가 클릭했을 때 응답 속도가 느리면 FID 점수가 나빠진다.

1. 무거운 코드로 인한 FID 악화​

document.querySelector("button")?.addEventListener("click", () => {
// 메인 스레드를 블로킹하는 무거운 반복문
let start = Date.now();
while (Date.now() - start < 3000) {
// 3초 동안 메인 스레드 차단 🚨
}
console.log("버튼 클릭 처리 완료!");
});

2. 동기적 렌더링​

<script>
// 스크립트가 로드되는 동안 HTML 파싱이 중단됨 🚨
let start = Date.now();
while (Date.now() - start < 5000) {
// 5초 동안 브라우저 차단
}
console.log("무거운 스크립트 실행 완료!");
</script>

3. 서드파티 스크립트​

<!-- 서드파티 광고 스크립트가 실행되면서 메인 스레드 차단 -->
<script src="https://third-party-ad.com/ad.js"></script>

4. 기준 점수​

5. 개선 방안​

1. 무거운 코드로 인한 FID 악화​

const worker = new Worker("worker.js");

document.querySelector("button")?.addEventListener("click", () => {
worker.postMessage("startTask"); // Web Worker에서 작업 처리
});

worker.onmessage = (event) => {
console.log("연산 결과:", event.data);
};
  • Web Worker를 사용해 무거운 작업을 메인 스레드에서 분리한다.

2. 동기적 렌더링​

window.addEventListener("DOMContentLoaded", () => {
console.log("DOM 로드 완료!");

setTimeout(() => {
console.log("초기 작업 실행!");
}, 0);
});
  • 비동기적 방법으로 setTimeout 사용

3. 서드파티 스크립트​

<script async src="https://third-party-ad.com/ad.js"></script>
  • 비동기적 로딩 또는 lazy loading 사용

5. 누적 레이아웃 이동(CLS)​

1. 정의​

웹 페이지의 예상치 못한 레이아웃 변경 정도를 측정하는 지표다. 사용자가 보고 있는 화면이 밀리거나 가려지면 점수가 나빠진다.

2. 의미​

렌더링 시작시 해당 위치에서 레이아웃 이동이 발생하거나 레이아웃 발생시 다른 요소의 시작 위치에 영향을 끼치면 점수가 나빠진다.

3. 예제​

동적 혹은 비동기적으로 무언가를 추가할 때나 이미지 크기를 지정하지 않아 사용자에게 부정적인 영향을 끼칠 수 있다.

1. 이미지 크기 미지정​

<img src="image.jpg">  <!-- width/height 없음 -->
  • 이미지가 로드되면서 크기가 바뀌어 콘텐츠가 밀려난다.

2. 동적으로 추가되는 배너​

<div id="ad-container"></div> 

<script>
setTimeout(() => {
document.getElementById("ad-container").innerHTML =
'<img src="ad.jpg" width="300" height="250">';
}, 3000); // 3초 후 광고가 삽입되면서 콘텐츠가 밀려남!
</script>
  • 배너가 나중에 나오면서 기존 컨텐츠를 밀어낸다.

3. 비동기적으로 추가되는 요소​

<p>이건 중요 콘텐츠입니다.</p>
<img src="large-image.jpg" loading="lazy"> <!-- Lazy Loading으로 늦게 로드됨 -->
  • 이미지가 처음에 안 보이다가 로딩되면서 컨텐츠가 밀려난다.

4. 기준 점수​

  • 영향분율: 레이아웃 이동이 발생한 요소의 높이와 뷰포트 높이의 비율
  • 거리분율: 이동이 발생한 요소가 움직인 거리와 뷰포트 높이의 비율
  • 영향분율 x 거리분율 로 계산해 최종 점수를 계산한다.

5. 개선 방안​

1. 이미지 크기 미지정​

<img src="image.jpg" width="600" height="400"> <!-- 고정 크기 지정 -->
  • 이미지가 들어갈 고정 공간을 미리 확보한다.

2. 동적으로 추가되는 배너​

<div id="ad-container" style="width: 300px; height: 250px;"></div> <!-- 고정 크기 설정 -->
  • 배너가 들어갈 고정 공간을 미리 확보한다.

3. 비동기적으로 추가되는 요소​

<img src="large-image.jpg" loading="lazy" style="min-height: 300px;">
  • Lazy Loading을 사용할 경우 min-height 설정

4. 폰트 로딩 최적화​

  • rel=preload 기능을 사용해 페이지에서 즉시 필요로 하는 리소스를 명시한다.

  • font-family 설정을 추가해 폰트를 불러올 수 있다.

6. 성능 확인에 중요한 지표들​

1. 최초 바이트까지의 시간(Time To First Byte, TTFB)​

페이지를 요청했을 때 최초의 응답이 오는 바이트가 얼마나 걸리는지를 측정하는 지표이다.

2. 최초 콘텐츠풀 페인트(First Contentful Paint, FCP)​

페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간을 측정하는 지표이다. 즉 웹 사이트에 뭐라도 뜨기 시작한 시점이다.

작성하고 느낀 점​

좋았던 점: 간단하지 않겠지만 웹 페이지 성능을 최적화 시키는 방법을 알게 되었다.

배운 점: 성능 개선에 관심을 가져야 하는 이유와 실행하는 방법에 대해 배웠다.

아쉬운 점: 책의 예제로는 이해를 돕기 어려워 외부 자료를 활용해야 했지만 찾는 과정에서 다른 방법들을 많이 알게 되었다.

향후 계획: 핵심 웹 지표 외에 성능 측정을 위한 방법을 배워볼 것이다.